/*
 * 巴法科技:2022-11-22
 * 支持巴法app和小程序配网，长按按键可重新配网
 */
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <AceButton.h>
#include <ESP8266httpUpdate.h>
using namespace ace_button;
WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;
// 巴法云服务器地址默认即可
#define TCP_SERVER_ADDR "bemfa.com"
// 服务器端口，tcp创客云端口8344
#define TCP_SERVER_PORT "8344"

//*****可以修改的地方******//
String aptype = "001";                                // 001插座类型，002灯类型，003风扇类型，004传感器，005空调，006开关，009窗帘
String verSion = "3";                                 // 1是mqtt协议，3是tcp协议,5是mqtt V2 版本，7是 tcp V2 版本
String adminID = "";                                  // 默认空即可。企业id,建议企业用户配置，用户会自动绑定到该企业下，获取id方法见接入文档5.17节
const int LedBlink = 16;                               // 指示灯引脚，可自行修改，如果没有指示灯，建议删除指示灯相关代码
const int buttonPin = 0;                             // 定义按钮引脚
const int shuiyaPin = A0;                              // 定义水压引脚
const int zengyaPin = 5;                              // 定义增压泵引脚
const int zengyazhuangtaiPin = 4;                     // 定义增压状态引脚
const int kaiguanPin = 2;                             // 定义水开关引脚

int wenduValue = 0;
int shuiyaValue = 0;                                    //水压值
int failCount = 0;                                     // 定义失败连接次数
bool zengyaState = true;                                  // 增压开关状态
bool zengyazhuangtaiState = true;                         // 增压状态
bool kaiguanState = true;                                  // 开关 状态

String upUrl = "http://bin.bemfa.com/b/3BcNDg5MmJkN2ZlMDA1ZWNiYmNjZjM1OTI5MTU3ZWM3ZTc=ZengYaBeng.bin"; // OTA固件链接，请替换为自己的固件链接，如果接收到msg=update，开始执行固件升级

//**********************//

// 最大字节数
#define MAX_PACKETSIZE 512
// 设置心跳值30s
#define KEEPALIVEATIME 2*1000

// tcp客户端相关初始化，默认即可
WiFiClient TCPclient;
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;    // 心跳
unsigned long preTCPStartTick = 0; // 连接
bool preTCPConnected = false;
String topicMac = "ZengYaBeng";

// 相关函数初始化
// 连接WIFI
void doWiFiTick();
void startSTA();

// TCP初始化连接
void doTCPClientTick();
void startTCPClient();
void sendtoTCPServer(String p);

// 增压控制函数
void turnOnzengya();
void turnOffzengya();
// 开关控制函数
void turnOnkaiguan();
void turnOffkaiguan();

int httpCode = 0;
String UID = "4892bd7fe005ecbbccf35929157ec7e7";
String TOPIC = "ZengYaBeng";
#define HOST_NAME "bemfa"
char config_flag = 0;
#define MAGIC_NUMBER 0xAA
/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  char stassid[32];
  char stapsw[64];
  char cuid[40];
  char ctopic[32];
  uint8_t reboot;
  uint8_t magic;
};

config_type config;
void initWiFi();
void restoreFactory();

// 当升级开始时，打印日志
void update_started()
{
  Serial.println("CALLBACK:  HTTP update process started");
}

// 当升级结束时，打印日志
void update_finished()
{
  Serial.println("CALLBACK:  HTTP update process finished");
}

// 当升级中，打印日志
void update_progress(int cur, int total)
{
  Serial.println(map(cur, 0, total, 0, 100));
  // sendtoTCPServer(
  //       "cmd=2&uid=" + UID + "&topic=" + TOPIC + "&msg=update#" + map(cur, 0, total, 0, 100) + "%#\r\n"); // 推送消息
}


// 当升级失败时，打印日志
void update_error(int err)
{
  Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
  
}

/**
 * 固件升级函数
 * 在需要升级的地方，加上这个函数即可，例如setup中加的updateBin();
 * 原理：通过http请求获取远程固件，实现升级
 */
void updateBin()
{
  Serial.println("start update");
  WiFiClient UpdateClient;

  ESPhttpUpdate.onStart(update_started);     // 当升级开始时
  ESPhttpUpdate.onEnd(update_finished);      // 当升级结束时
  ESPhttpUpdate.onProgress(update_progress); // 当升级中
  ESPhttpUpdate.onError(update_error);       // 当升级失败时

  t_httpUpdate_return ret = ESPhttpUpdate.update(UpdateClient, upUrl);
  switch (ret)
  {
  case HTTP_UPDATE_FAILED: // 当升级失败
    Serial.println("[update] Update failed.");
    break;
  case HTTP_UPDATE_NO_UPDATES: // 当无升级
    Serial.println("[update] Update no Update.");
    break;
  case HTTP_UPDATE_OK: // 当升级成功
    Serial.println("[update] Update ok.");
    break;
  }
}

// 按钮配置接口
AceButton ledButton(buttonPin);
// 按键处理程序
void handleEvent(AceButton *button, uint8_t eventType,uint8_t)
{
  switch (eventType)
  {
  // 当短按时
  case AceButton::kEventReleased:
    Serial.println(F("Button: Pressed"));
    kaiguanState = !kaiguanState;            
    digitalWrite(kaiguanPin, kaiguanState); 
    if (kaiguanState == true)
    {
      Serial.println("low press off");
     
    }
    else
    {
      Serial.println("low press on");
      
    }

    break;
     // 当长按时
  case AceButton::kEventLongPressed:
    Serial.println(F("Button: Long Pressed"));
    zengyaState = !zengyaState;            // 改变led状态
    digitalWrite(zengyaPin, zengyaState); // 写入状态
    if (zengyaState == true)
    {
      Serial.println("low press off");
     
    }
    else
    {
      Serial.println("low press on");
      
    }
    
    
    break;
  }
}

static unsigned long buttonLastMillis = 0; // 时间戳，用于计算防抖
void IRAM_ATTR checkSwitch()
{
  unsigned long newMillis = millis(); // 获取当前时间戳

  // if (newMillis - buttonLastMillis > 30)
  // { // 检测短按，是否大于30ms
  //   Serial.println("low press !!!!!!!!");
  //   zengyaState = !zengyaState;            // 改变led状态
  //   digitalWrite(zengyaPin, zengyaState); // 写入状态
  // }
  buttonLastMillis = newMillis; // 重新计算防抖动
}
Ticker delayTimer;
void delayRestart(float t)
{
  delayTimer.attach(t, []()
                    { ESP.restart(); });
}




/**
 * 恢复出厂设置，清除存储的wifi信息
 */
void restoreFactory()
{
  Serial.println("Restore Factory....... ");
  config.magic = 0x00;
  strcpy(config.stassid, "");
  strcpy(config.stapsw, "");
  strcpy(config.cuid, "");
  strcpy(config.ctopic, "");
  delayRestart(1);
  while (1)
  {
    delay(100);
  }
}

/*
 *发送数据到TCP服务器
 */
void sendtoTCPServer(String p)
{

  if (!TCPclient.connected())
  {
    Serial.println("Client is not readly");
    return;
  }
  TCPclient.print(p);
  // Serial.println("[Send to TCPServer]:String");
  // Serial.println(p);
  preHeartTick = millis(); // 心跳计时开始，需要每隔60秒发送一次数据
}

/*
 *初始化和服务器建立连接
 */
void startTCPClient()
{
  if (TCPclient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT)))
  {
    Serial.print("\nConnected to server:");
    Serial.printf("%s:%d\r\n", TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT));

    String tcpTemp = "";                                       // 初始化字符串
    tcpTemp = "cmd=1&uid=" + UID + "&topic=" + TOPIC + "\r\n"; // 构建订阅指令
    sendtoTCPServer(tcpTemp);                                  // 发送订阅指令
    tcpTemp = "";                                              // 清空

    preTCPConnected = true;
    TCPclient.setNoDelay(true);
    failCount = 0;
  }
  else
  {
    failCount = failCount + 1;
    if (failCount > 2)
    { // 如果失败连接3次，重启系统
      delayRestart(0);
    }
    Serial.print("Failed connected to server:");
    Serial.println(TCP_SERVER_ADDR);
    TCPclient.stop();
    preTCPConnected = false;
  }
  preTCPStartTick = millis();
}

/*
 *检查数据，发送心跳
 */
void doTCPClientTick()
{
  // 检查是否断开，断开后重连
  if (WiFi.status() != WL_CONNECTED)
    return;

  if (!TCPclient.connected())
  { // 断开重连

    if (preTCPConnected == true)
    {

      preTCPConnected = false;
      preTCPStartTick = millis();
      Serial.println();
      Serial.println("TCP Client disconnected.");
      TCPclient.stop();
    }
    else if (millis() - preTCPStartTick > 1 * 1000) // 重新连接
      startTCPClient();
  }
  else
  {
    if (TCPclient.available())
    { // 收数据
      char c = TCPclient.read();
      TcpClient_Buff += c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();

      if (TcpClient_BuffIndex >= MAX_PACKETSIZE - 1)
      {
        TcpClient_BuffIndex = MAX_PACKETSIZE - 2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
    }
    if (millis() - preHeartTick >= KEEPALIVEATIME)
    { // 保持心跳
      wenduValue = analogRead(14);

      shuiyaValue = analogRead(shuiyaPin)*2;
      zengyazhuangtaiState = digitalRead(zengyazhuangtaiPin);// 写入工作状态
      sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + topicMac + "/up&msg=" + topicMac + "#" + zengyaState + "#" + kaiguanState + "#" + shuiyaValue + "#" + wenduValue + "#" + zengyazhuangtaiState+"#\r\n"); // 推送消息

    }
  }
  if ((TcpClient_Buff.length() >= 5) && (millis() - TcpClient_preTick >= 200))
  { // data ready
    TCPclient.flush();
    // Serial.print("Rev string: ");
    TcpClient_Buff.trim();          // 去掉首位空格
    // Serial.println(TcpClient_Buff); // 打印接收到的消息
    String getTopic = "";
    String getMsg = "";
    if (TcpClient_Buff.length() > 15)
    { // 注意TcpClient_Buff只是个字符串，在上面开头做了初始化 String TcpClient_Buff = "";
      // 此时会收到推送的指令，指令大概为 cmd=2&uid=xxx&topic=light002&msg=off
      int topicIndex = TcpClient_Buff.indexOf("&topic=") + 7;    // c语言字符串查找，查找&topic=位置，并移动7位，不懂的可百度c语言字符串查找
      int msgIndex = TcpClient_Buff.indexOf("&msg=");            // c语言字符串查找，查找&msg=位置
      getTopic = TcpClient_Buff.substring(topicIndex, msgIndex); // c语言字符串截取，截取到topic,不懂的可百度c语言字符串截取
      getMsg = TcpClient_Buff.substring(msgIndex + 5);           // c语言字符串截取，截取到消息
      // Serial.print("topic:------");
      // Serial.println(getTopic); // 打印截取到的主题值
      // Serial.print("msg:--------");
      // Serial.println(getMsg); // 打印截取到的消息值
      
      if (getMsg == "turnOnzengya")
      {                   // 如果是消息==打开
        turnOnzengya();      // 打开灯
        zengyaState = true; // 改变记录的状态
        sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + topicMac + "/up&msg=" + topicMac + "#" + zengyaState + "#" + kaiguanState + "#" + shuiyaValue + "#" + wenduValue + "#\r\n"); // 推送消息

      }
      else if (getMsg == "turnOffzengya")
      {                  // 如果是消息==关闭
        turnOffzengya();    // 关闭灯
        zengyaState = false; // 改变记录的状态
        sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + topicMac + "/up&msg=" + topicMac + "#" + zengyaState + "#" + kaiguanState + "#" + shuiyaValue + "#" + wenduValue + "#\r\n"); // 推送消息

      }
      else if (getMsg == "turnOnkaiguan")
      {                   // 如果是消息==打开
        turnOnkaiguan();      // 打开灯
        kaiguanState = true; // 改变记录的状态
        sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + topicMac + "/up&msg=" + topicMac + "#" + zengyaState + "#" + kaiguanState + "#" + shuiyaValue + "#" + wenduValue + "#\r\n"); // 推送消息

      }
      else if (getMsg == "turnOffkaiguan")
      {                  // 如果是消息==关闭
        turnOffkaiguan();    // 关闭灯
        kaiguanState = false; // 改变记录的状态
        sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + topicMac + "/up&msg=" + topicMac + "#" + zengyaState + "#" + kaiguanState + "#" + shuiyaValue + "#" + wenduValue + "#\r\n"); // 推送消息

      }
      else if (getMsg == "update")
      {
        Serial.println("[update] Update Start......");
        updateBin();
      }
      TcpClient_Buff = "";
      TcpClient_BuffIndex = 0;
    }
  }  
}
/**
 * 初始化wifi信息，并连接路由器网络
 * 
 */
#define wifi_name "万物互联"                     // WIFI名称，区分大小写，不要写错
#define wifi_password "15367660"                 // WIFI密码
void initWiFi()
{

  if (WiFi.status() != WL_CONNECTED)
  {
    WiFi.disconnect();                         // 断开连接
    WiFi.mode(WIFI_STA);                       // STA模式
    WiFi.begin(wifi_name, wifi_password); // 连接路由器

  }
  int num = 0;
  while (WiFi.status() != WL_CONNECTED && num < 120)
  { // 检查是否连接成功
    delay(500);
    num = num + 1;
  }
  Serial.println("wifi config ok");
}
/**************************************************************************
                                 WIFI
***************************************************************************/
/*
  WiFiTick
  检查是否需要初始化WiFi
  检查WiFi是否连接上，若连接成功启动TCP Client
  控制指示灯
*/
void doWiFiTick()
{
  static bool startSTAFlag = false;
  static bool taskStarted = false;
  static uint32_t lastWiFiCheckTick = 0;

  if (!startSTAFlag)
  {
    startSTAFlag = true;
    initWiFi();
    Serial.printf("Heap size:%d\r\n", ESP.getFreeHeap());
  }

  // 未连接1s重连
  if (WiFi.status() != WL_CONNECTED)
  {
    if (millis() - lastWiFiCheckTick > 1000)
    {
      lastWiFiCheckTick = millis();
    }
  }
  // 连接成功建立
  else
  {
    if (taskStarted == false)
    {
      taskStarted = true;
      Serial.print("\r\nGet IP Address: ");
      Serial.println(WiFi.localIP());
      startTCPClient();
    }
  }
}
// 打开
void turnOnzengya()
{
  Serial.println("Turn ON");
  digitalWrite(zengyaPin, HIGH);

}
// 关闭
void turnOffzengya()
{
  Serial.println("Turn OFF");
  digitalWrite(zengyaPin, LOW);

}
// 打开
void turnOnkaiguan()
{
  Serial.println("Turn ON");
  digitalWrite(kaiguanPin, HIGH);
}
// 关闭
void turnOffkaiguan()
{
  Serial.println("Turn OFF");
  digitalWrite(kaiguanPin, LOW);
}

// 初始化，相当于main 函数
void setup()
{
  Serial.begin(115200);

  pinMode(buttonPin, INPUT_PULLUP);                                       // 设置led引脚为输入引脚
  attachInterrupt(digitalPinToInterrupt(buttonPin), checkSwitch, RISING); // 设置中断
  pinMode(zengyaPin, OUTPUT);
  pinMode(kaiguanPin, OUTPUT);
  pinMode(zengyazhuangtaiPin, INPUT);
  digitalWrite(zengyaPin, zengyaState);
  digitalWrite(kaiguanPin, kaiguanState); // 写入默认状态
  pinMode(LedBlink, OUTPUT);
  digitalWrite(LedBlink, LOW); // 指示灯引脚
  Serial.println(" ");
  Serial.println("Beginning...");
  // 按键配置
  ButtonConfig *buttonConfig = ButtonConfig::getSystemButtonConfig();
  buttonConfig->setEventHandler(handleEvent);
  buttonConfig->setFeature(ButtonConfig::kFeatureLongPress);
  buttonConfig->setFeature(ButtonConfig::kFeatureRepeatPress);
  buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterLongPress);
  buttonConfig->setLongPressDelay(3000); // 长按时间5秒

  detachInterrupt(digitalPinToInterrupt(buttonPin)); // 删除外部中断
  digitalWrite(LedBlink, LOW);                       // 指示灯引脚
}

// 循环
void loop()
{
  doWiFiTick();      // 检查wifi
  doTCPClientTick(); // tcp消息接收
  ledButton.check(); // 按键检查
  
}
